#include "cheetah/model/triangle_mesh.h"
#include "cheetah/core/error.h"
#include "cheetah/core/math.h"

namespace cheetah {
namespace model {
using namespace core;
TriangleMesh* CreateBoxMesh(const Point &pos1, const Point &pos2) {
  int fn = 12, vn = 8;
  Point *points = new Point[vn];
  int *vert_indices = new int[fn * 3];
  points[0] = Point(pos1.x, pos1.y, pos1.z);
  points[1] = Point(pos2.x, pos1.y, pos1.z);
  points[2] = Point(pos2.x, pos2.y, pos1.z);
  points[3] = Point(pos1.x, pos2.y, pos1.z);
  points[4] = Point(pos1.x, pos1.y, pos2.z);
  points[5] = Point(pos2.x, pos1.y, pos2.z);
  points[6] = Point(pos2.x, pos2.y, pos2.z);
  points[7] = Point(pos1.x, pos2.y, pos2.z);

  int idx[36] = { 0, 1, 3, 
                  1, 2, 3, 
                  5, 4, 7, 
                  5, 7, 6, 
                  4, 0, 3, 
                  4, 3, 7, 
                  1, 5, 2, 
                  5, 6, 2, 
                  4, 5, 1, 
                  4, 1, 0, 
                  6, 7, 2, 
                  7, 3, 2 };
  memcpy(vert_indices, idx, sizeof(idx));
  Normal *normals = new Normal[vn];
  FinalizeModel(points, vn, vert_indices, &fn, normals);
  return new TriangleMesh(points, normals, NULL, vert_indices, vn, fn);
}

enum { kCacheSize = 240 };
//inline void ProcessQuad(const int slices, const int n_idx, int &vert_idx,
//                        int *indices, int &idx) {
//  indices[idx++] = vert_idx;
//  indices[idx++] = n_idx;
//  indices[idx++] = vert_idx - slices;
//  indices[idx++] = n_idx;
//  indices[idx++] = n_idx - slices;
//  indices[idx++] = vert_idx - slices;
//  ++vert_idx;
//}
inline void ProcessQuad(const int slices, const int n_idx, int &vert_idx,
                        int *indices, int &idx) {
  indices[idx++] = vert_idx;
  indices[idx++] = n_idx;
  indices[idx++] = vert_idx + slices;
  indices[idx++] = n_idx;
  indices[idx++] = n_idx + slices;
  indices[idx++] = vert_idx + slices;
  ++vert_idx;
}

TriangleMesh *CreateSphere(const double radius, int slices, int stacks) {
  if (slices >= kCacheSize) slices = kCacheSize - 1;
  if (stacks >= kCacheSize) stacks = kCacheSize - 1;
  if (slices < 2 || stacks < 1 || radius < 0.0) {
    Error("invalid arguments");
    return NULL;
  }

  float sin_cache_1a[kCacheSize];
  float cos_cache_1a[kCacheSize];
  float sin_cache_1b[kCacheSize];
  float cos_cache_1b[kCacheSize];
  float sin_cache_2a[kCacheSize];
  float cos_cache_2a[kCacheSize];
  float sin_cache_2b[kCacheSize];
  float cos_cache_2b[kCacheSize];
  
  float deg_step = 2 * math::kPi / slices;
  float deg = 0.f;
  for (int j = 0; j < slices; ++j, deg += deg_step) {
    sin_cache_1a[j] = sin(deg);
    cos_cache_1a[j] = cos(deg);
    
    // normal
    sin_cache_2a[j] = sin_cache_1a[j];
    cos_cache_2a[j] = cos_cache_1a[j];  
  }
  sin_cache_1a[slices] = sin_cache_1a[0];
  cos_cache_1a[slices] = cos_cache_1a[0];

  deg_step = math::kPi / stacks;
  deg = 0.f;
  for (int i = 0; i <= stacks; ++i, deg += deg_step) {    
    // normal
    sin_cache_2b[i] = sin(deg);
    cos_cache_2b[i] = cos(deg);

    sin_cache_1b[i] = radius * sin_cache_2b[i];
    cos_cache_1b[i] = radius * cos_cache_2b[i];    
  }
  /* Make sure it comes to a point */
  sin_cache_1b[0] = 0;
  sin_cache_1b[stacks] = 0;
  const int n_vertices = 2 + (stacks - 1) * slices;
  const int n_triangles = (stacks - 1) * slices * 2;
  Point *vertices = new Point[n_vertices];
  Normal *normals = new Normal[n_vertices];
  int *indices = new int[n_triangles * 3];
  
  vertices[0].x = 0; 
  vertices[0].y = 0; 
  vertices[0].z = cos_cache_1b[0];
  normals[0].x = 0;
  normals[0].y = 0;
  normals[0].z = cos_cache_2b[0];
  int vert_idx = 1;
  for (int i = 1; i < stacks; ++i) {
    for (int j = 0; j < slices; ++j, ++vert_idx) {
      vertices[vert_idx].x = cos_cache_1a[j] * sin_cache_1b[i];
      vertices[vert_idx].y = sin_cache_1a[j] * sin_cache_1b[i];
      vertices[vert_idx].z = cos_cache_1b[i];
      normals[vert_idx].x = cos_cache_2a[j] * sin_cache_2b[i];
      normals[vert_idx].y = sin_cache_2a[j] * sin_cache_2b[i];
      normals[vert_idx].z = cos_cache_2b[i];      
    }
  }
  vertices[vert_idx].x = 0; 
  vertices[vert_idx].y = 0;
  vertices[vert_idx].z = cos_cache_1b[stacks];
  normals[vert_idx].x = 0;
  normals[vert_idx].y = 0;
  normals[vert_idx].z = cos_cache_2b[stacks];

  //[0 1 2], [0 2 3], [0 3 4]
  int idx = 0;  
  for (vert_idx = 1; vert_idx < slices; ) {
    indices[idx++] = 0;
    indices[idx++] = vert_idx;
    indices[idx++] = ++vert_idx;
  }  
  indices[idx++] = 0; indices[idx++] = vert_idx; indices[idx++] = 1;

  vert_idx = 1;
  for (int i = 1; i < stacks - 1; ++i) {
    for (int j = 1; j < slices; ++j) {
      int n_idx = vert_idx + 1;
      ProcessQuad(slices, n_idx, vert_idx, indices, idx);
    }
    int n_idx = vert_idx + 1 - slices;
    ProcessQuad(slices, n_idx, vert_idx, indices, idx);
  } 
  
  int n_idx = vert_idx + slices;
  for (int j = 1; j < slices; ++j) {
    indices[idx++] = vert_idx;
    indices[idx++] = n_idx;
    indices[idx++] = ++vert_idx;
  }
  indices[idx++] = vert_idx;
  indices[idx++] = n_idx;
  indices[idx++] = n_idx - slices;
  return new TriangleMesh(vertices, normals, NULL, indices, 
                          n_vertices, n_triangles);
}

TriangleMesh *CreateCylinder(const double base_radius, 
                             const double top_radius, 
                             const double height,
                             int slices, int stacks) {
  if (slices >= kCacheSize) slices = kCacheSize - 1;
  if (stacks >= kCacheSize) stacks = kCacheSize - 1;
  if (slices < 2 || stacks < 1 || top_radius < 0.0 || base_radius < 0.0) {
    Error("invalid arguments");
    return NULL;
  }

  /* Compute length (needed for normal calculations) */
  float delta_radius = base_radius - top_radius;
  float length = sqrtf(delta_radius*delta_radius + height*height);
  if (length == 0.0) {
    Error("invalid arguments");
	  return NULL;
  }

  float sin_cache1[kCacheSize];
  float cos_cache1[kCacheSize];
  float sin_cache2[kCacheSize];
  float cos_cache2[kCacheSize];

  if (slices >= kCacheSize) slices = kCacheSize - 1;  
  float z_normal = delta_radius / length;
  float xy_normal_ratio = height / length;

  const float deg_step = 2 * math::kPi / slices;
  float deg = 0.f;
  for (int i = 0; i < slices; ++i, deg += deg_step) {                
    sin_cache1[i] = sin(deg);
    cos_cache1[i] = cos(deg);
    // for normal
    sin_cache2[i] = xy_normal_ratio * sin_cache1[i];
    cos_cache2[i] = xy_normal_ratio * cos_cache1[i];
  }  

  sin_cache1[slices] = sin_cache1[0];
  cos_cache1[slices] = cos_cache1[0];  
  sin_cache2[slices] = sin_cache2[0];
  cos_cache2[slices] = cos_cache2[0];

  const int n_vertices = (stacks + 1) * slices;
  const int n_triangles = stacks * 2 * slices;
  Point *vertices = new Point[n_vertices];
  Normal *normals = new Normal[n_vertices];
  int *indices = new int[n_triangles * 3];

  const float z_step = height / stacks;
  float z_low = 0;
  const float radius_step = -delta_radius / stacks;
  float radius_low = base_radius;
  int vert_idx = 0;
  for (int j = 0; j <= stacks; ++j) {
    for (int i = 0; i < slices; ++i, ++vert_idx) {
      vertices[vert_idx].x = radius_low * sin_cache1[i];
      vertices[vert_idx].y = radius_low * cos_cache1[i];
      vertices[vert_idx].z = z_low;

      normals[vert_idx].x = sin_cache2[i];
      normals[vert_idx].y = cos_cache2[i];
      normals[vert_idx].z = z_normal;
    }
    z_low += z_step;
    radius_low += radius_step;
  }
  int idx = 0;
  vert_idx = 0;
  for (int j = 0; j < stacks; ++j) {
    for (int i = 0; i < slices - 1; ++i) {
      int n_idx = vert_idx + 1;
      ProcessQuad(slices, n_idx, vert_idx, indices, idx);
    }
    int n_idx = vert_idx + 1 - slices;
    ProcessQuad(slices, n_idx, vert_idx, indices, idx);
  }

  return new TriangleMesh(vertices, normals, NULL, indices, 
                          n_vertices, n_triangles);
}


//switch (qobj->drawStyle) {
//case GLU_FILL:
//  /* Note:
//  ** An argument could be made for using a TRIANGLE_FAN for the end
//  ** of the cylinder of either radii is 0.0 (a cone).  However, a
//  ** TRIANGLE_FAN would not work in smooth shading mode (the common
//  ** case) because the normal for the apex is different for every
//  ** triangle (and TRIANGLE_FAN doesn't let me respecify that normal).
//  ** Now, my choice is GL_TRIANGLES, or leave the GL_QUAD_STRIP and
//  ** just let the GL trivially reject one of the two triangles of the
//  ** QUAD.  GL_QUAD_STRIP is probably faster, so I will leave this code
//  ** alone.
//  */
//  for (j = 0; j < stacks; j++) {
//    zLow = j * height / stacks;
//    zHigh = (j + 1) * height / stacks;
//    radiusLow = baseRadius - deltaRadius * ((float) j / stacks);
//    radiusHigh = baseRadius - deltaRadius * ((float) (j + 1) / stacks);
//
//    glBegin(GL_QUAD_STRIP);
//    for (i = 0; i <= slices; i++) {
//      switch(qobj->normals) {
//      case GLU_FLAT:
//        glNormal3f(sinCache3[i], cosCache3[i], zNormal);
//        break;
//      case GLU_SMOOTH:
//        glNormal3f(sinCache2[i], cosCache2[i], zNormal);
//        break;
//      case GLU_NONE:
//      default:
//        break;
//      }
//      if (qobj->orientation == GLU_OUTSIDE) {
//        if (qobj->textureCoords) {
//          glTexCoord2f(1 - (float) i / slices,
//            (float) j / stacks);
//        }
//        glVertex3f(radiusLow * sinCache[i],
//          radiusLow * cosCache[i], zLow);
//        if (qobj->textureCoords) {
//          glTexCoord2f(1 - (float) i / slices,
//            (float) (j+1) / stacks);
//        }
//        glVertex3f(radiusHigh * sinCache[i],
//          radiusHigh * cosCache[i], zHigh);
//      } else {
//        if (qobj->textureCoords) {
//          glTexCoord2f(1 - (float) i / slices,
//            (float) (j+1) / stacks);
//        }
//        glVertex3f(radiusHigh * sinCache[i],
//          radiusHigh * cosCache[i], zHigh);
//        if (qobj->textureCoords) {
//          glTexCoord2f(1 - (float) i / slices,
//            (float) j / stacks);
//        }
//        glVertex3f(radiusLow * sinCache[i],
//          radiusLow * cosCache[i], zLow);
//      }
//    }
//    glEnd();
//  }
//  break;
//case GLU_POINT:
//  glBegin(GL_POINTS);
//  for (i = 0; i < slices; i++) {
//    switch(qobj->normals) {
//    case GLU_FLAT:
//    case GLU_SMOOTH:
//      glNormal3f(sinCache2[i], cosCache2[i], zNormal);
//      break;
//    case GLU_NONE:
//    default:
//      break;
//    }
//    sintemp = sinCache[i];
//    costemp = cosCache[i];
//    for (j = 0; j <= stacks; j++) {
//      zLow = j * height / stacks;
//      radiusLow = baseRadius - deltaRadius * ((float) j / stacks);
//
//      if (qobj->textureCoords) {
//        glTexCoord2f(1 - (float) i / slices,
//          (float) j / stacks);
//      }
//      glVertex3f(radiusLow * sintemp,
//        radiusLow * costemp, zLow);
//    }
//  }
//  glEnd();
//  break;
//case GLU_LINE:
//  for (j = 1; j < stacks; j++) {
//    zLow = j * height / stacks;
//    radiusLow = baseRadius - deltaRadius * ((float) j / stacks);
//
//    glBegin(GL_LINE_STRIP);
//    for (i = 0; i <= slices; i++) {
//      switch(qobj->normals) {
//      case GLU_FLAT:
//        glNormal3f(sinCache3[i], cosCache3[i], zNormal);
//        break;
//      case GLU_SMOOTH:
//        glNormal3f(sinCache2[i], cosCache2[i], zNormal);
//        break;
//      case GLU_NONE:
//      default:
//        break;
//      }
//      if (qobj->textureCoords) {
//        glTexCoord2f(1 - (float) i / slices,
//          (float) j / stacks);
//      }
//      glVertex3f(radiusLow * sinCache[i],
//        radiusLow * cosCache[i], zLow);
//    }
//    glEnd();
//  }
//  /* Intentionally fall through here... */
//case GLU_SILHOUETTE:
//  for (j = 0; j <= stacks; j += stacks) {
//    zLow = j * height / stacks;
//    radiusLow = baseRadius - deltaRadius * ((float) j / stacks);
//
//    glBegin(GL_LINE_STRIP);
//    for (i = 0; i <= slices; i++) {
//      switch(qobj->normals) {
//      case GLU_FLAT:
//        glNormal3f(sinCache3[i], cosCache3[i], zNormal);
//        break;
//      case GLU_SMOOTH:
//        glNormal3f(sinCache2[i], cosCache2[i], zNormal);
//        break;
//      case GLU_NONE:
//      default:
//        break;
//      }
//      if (qobj->textureCoords) {
//        glTexCoord2f(1 - (float) i / slices,
//          (float) j / stacks);
//      }
//      glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i],
//        zLow);
//    }
//    glEnd();
//  }
//  for (i = 0; i < slices; i++) {
//    switch(qobj->normals) {
//    case GLU_FLAT:
//    case GLU_SMOOTH:
//      glNormal3f(sinCache2[i], cosCache2[i], 0.0);
//      break;
//    case GLU_NONE:
//    default:
//      break;
//    }
//    sintemp = sinCache[i];
//    costemp = cosCache[i];
//    glBegin(GL_LINE_STRIP);
//    for (j = 0; j <= stacks; j++) {
//      zLow = j * height / stacks;
//      radiusLow = baseRadius - deltaRadius * ((float) j / stacks);
//
//      if (qobj->textureCoords) {
//        glTexCoord2f(1 - (float) i / slices,
//          (float) j / stacks);
//      }
//      glVertex3f(radiusLow * sintemp,
//        radiusLow * costemp, zLow);
//    }
//    glEnd();
//  }
//  break;
//default:
//  break;
//}

} // model
} // cheetah
